package distributed.entity;

import java.sql.Date;

/**
 * Entity for holding data about an event.
 * @author Sam Jaffe, Marcus Menghini
 *
 */
public class Event {
	
	/** The event question */
	String title;
	
	/** The array of choices */
	Choice[] lines;
	
	/** The array of edges */
	Edge[] edges;
	
	/** The array of users */
	User[] users;
	
	/** Is the event open */
	boolean isOpen;
	
	/** Is the event finished */
	boolean isDone;
	
	/** Has the event started (adding edges) */
	boolean isStart = false;
	
	/** What is the creation date */
	Date creationDate;
	
	/** The index of the next edge */
	int addIndex = 0;
	
	/** The number of users actually connected */
	int numUsers = 0;

	/**
	 * @param n integer, the number of choices there will be.
	 * @param m integer, the number of edges each user can place.
	 * @param question string, the question that the choices are answering.
	 * @param isOpen boolean, is the Event open to participation.
	 */
	public Event (int n, int m, String question, boolean isOpen) {
		this.lines = new Choice[n];
		this.users = new User[n];
		this.edges = new Edge[n*m];
		for (int i = 0; i < n; i++)
			lines[i] = new Choice(i+1);
		this.title = question;
		this.isOpen = isOpen;
		creationDate = new Date(System.currentTimeMillis());
	}
	
	/**
	 * Attempts to add a given edge to the array of edges stored in this event.
	 * @param e The Edge we are attempting to add.
	 * @return Returns true if the edge was added.
	 */
	public int addEdge(Edge e) {
		close(); //Close the event, for safety (also starts it)
		if (isValidEdge(e)) { //Check that this is a valid edge
			edges[addIndex] = e; //If so, add it
			addIndex++;
			if (addIndex >= (numUsers == 0 ? lines.length : numUsers)*getRounds()) {
				isDone = true; //If we've done everything, set isDone to true
			}
			if (numUsers == 0)
				return 0; //Return 0 if there are no users (div by 0 catch)
			else
				return addIndex%numUsers; //Else modulo num users for the next index
		}
		return -1; //Return -1 if the edge is invalid
	}

	/**
	 * Checks if a given edge is too close to other edges already in this event.
	 * @param e The Event to check the validity of.
	 * @return Returns true if there is no conflict, else false.
	 */
	public boolean isValidEdge(Edge e) {
		for (int i = 0; i < addIndex; i++)
			// Go through until our condition is triggered or we're out of edges
			if (Math.abs(edges[i].getIndex() - e.sourceLine) <= 1
					&& Math.abs(edges[i].getHeight() - e.height) <= 7)
				// We don't care if the lines are far away, but if they aren't,
				//check if they're too close
				return false;
		return true;
	}
	
	/**
	 * @param position integer, sets the x position for the choice.
	 * @param choice string, sets the name of the choice.
	 * @return true if the choice was set successfully, false if it failed.
	 */
	public boolean setChoice(int position, String choice) {
		if (choice.equals("")) // Prevent the entering of the empty String as an input
			return false;
		for (int i = 0; i < lines.length; i++) // Make sure that none of the
			//lines share the same choice.
			if (i != position && choice.equalsIgnoreCase(lines[i].getChoice())) 
				// Ignore the target line
				return false;
		lines[position].setChoice(choice); // If we got through that, now we can set the choice
		if (!isOpen) { //If the event is not open
			for (Choice c : lines) //If every choice is filled now
				if (c.choice == null)
					return true;
			close(); //Start up
		}
		return true;
	}
	
	/**
	 * @param id string, passes the userID.
	 * @return integer that determines what is next.
	 */
	public int next(String id) {
		int check = lines.length;
		if (users[0] == null)
			return 0; //Zero for the mod
		
		if (isStart) return -1; //Once we've started, don't let anyone in
		
		for (int i = 0; i < lines.length; i++) //Get the earliest line index
			if (lines[i].choice == null || lines[i].choice.equals("")) {
				check = i;
				break;
			}
				
		// Get the earliest open user index
		for (int i = 0; i < users.length; i++)
			if (users[i] == null)
				return Math.max(check, i); 
				//Return the highest point of this point vs. line option
			else if (users[i].getID().equals(id))
				return users[i].getIndex(); // If we've already been here, return our original index
		
		//If there are no user slots, return -1
		return -1;
	}
	
	/**
	 * @param user User, returns the User that is signing in.
	 * @return true if the user exists or now exists, false if not.
	 */
	public boolean signIn(User user) {
		for (int i = 0; i < users.length; i++) {
			if (users[i] == null) { //Log in to a null slot
				users[i] = user;
				numUsers++; //Increment the number of users
				return true;
			}
			else if (users[i].getID().equals(user.getID()))
				return true; //Or go if we find our id
			else if (users[i].getName().equals(user.getName()))
				return false; //But not is !id but names match
		}
		return false; //And is that fails, return false
	}

	/**
	 * @return isOpen boolean, lets you know if the event is open.
	 */
	public boolean isOpen() {
		return isOpen;
	}
	
	/**
	 * @return isDone boolean, lets you know if the event is finished.
	 */
	public boolean done() {
		return isDone;
	}
	/**
	 * @return title string, lets you know what the question is.
	 */
	public String getQuestion() {
		return title;
	}
	
	/**
	 * @return lines Line[*], returns the lines that exist.
	 */
	public Choice[] getLines() {
		return lines;
	}
	
	/**
	 * @return edges Edge[*], returns all the edges.
	 */
	public Edge[] getEdges() {
		return edges;
	}

	/**
	 * @return users User[*], returns all users.
	 */
	public User[] getUsers() {
		return users;
	}
	
	/**
	 * @return numUsers integer, returns the number of users.
	 */
	public int getNumUsers() {
		return numUsers;
	}
	
	/**
	 * @return lines.length integer, the height of the lines.
	 */
	public int getCount() {
		return lines.length;
	}
	
	/**
	 * @return edges.length/getCount() integer, returns the number of rounds that will exist.
	 */
	public int getRounds() {
		return edges.length/getCount();
	}

	/**
	 * Closes the event.
	 */
	public void close() {
		isOpen = false;
		isStart = true;
	}

	/**
	 * @return creationDate Date, returns the date that the event was made.
	 */
	public Date getCreationDate() {
		return creationDate;
	}
	
	/**
	 * @param newDate Date, passes the date that the event was created on.
	 */
	public void setCreationDate(Date newDate) {
		creationDate = newDate;
	}

	/**
	 * @return isStart boolean, returns if the event has started.
	 */
	public boolean isStart() {
		return isStart;
	}
	
	/**
	 * sets the isStart variable to false.
	 */
	public void unStart() {
		this.isStart = false;
	}

	/**
	 * Used to make sure DC'd users don't freeze the event.
	 * @param next integer, removes the user from the list.
	 */
	public void removeUser(int next) {
		numUsers--;
		for (int i = next; i < numUsers; i++)
			users[i+1] = users[i];
		users[numUsers] = null;
	}
}
